package org.infodavid.common.io;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;

import javax.activation.MimetypesFileTypeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The Class IOUtil.
 */
public final class IOUtil {

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(IOUtil.class);

	/** The singleton. */
	private static WeakReference<IOUtil> singleton = null;

	/**
	 * returns the singleton.
	 * @return <code>IOUtil</code>
	 */
	public static synchronized IOUtil getInstance() {
		if (singleton == null || singleton.get() == null) {
			singleton = new WeakReference<IOUtil>(new IOUtil());
		}

		return singleton.get();
	}

	/** The is debug enabled. */
	private final boolean isDebugEnabled = LOGGER.isDebugEnabled();

	/**
	 * Instantiates a new utilities.
	 */
	private IOUtil() {
		super();
	}

	/**
	 * Gets the mime type.
	 * @param file the file
	 * @return the mime type
	 */
	public String getMimeType(final File file) {
		return new MimetypesFileTypeMap().getContentType(file);
	}

	/**
	 * Read file.
	 * @param file the file
	 * @return the byte[]
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public byte[] readFile(final File file) throws IOException {
		if (isDebugEnabled) {
			LOGGER.debug("Reading file: " + file);
		}

		InputStream is = null;
		byte[] bytes = null;

		try {
			is = new FileInputStream(file);
			final long length = file.length();

			if (length > Integer.MAX_VALUE) {
				throw new IOException("File " + file.getName() + " is too large: " + length);
			}

			bytes = new byte[(int)length];
			int offset = 0;
			int numRead;

			while (offset < bytes.length
			    && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
				offset += numRead;
			}

			if (offset < bytes.length) {
				throw new IOException("Could not completely read file " + file.getName());
			}
		}
		finally {
			if (is != null) {
				try {
					is.close();
				}
				catch (final Exception e) {
					if (isDebugEnabled) {
						LOGGER.debug("An error occurs while closing stream", e);
					}
				}
			}
		}

		return bytes;
	}

	/**
	 * Read file.
	 * @param is the input stream
	 * @return the byte[]
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public byte[] readFile(final InputStream is) throws IOException {
		LOGGER.debug("Reading stream");

		byte[] bytes = null;

		final byte readBuf[] = new byte[512 * 1024];

		try {
			final ByteArrayOutputStream out = new ByteArrayOutputStream();
			int readCnt = is.read(readBuf);

			while (readCnt > 0) {
				out.write(readBuf, 0, readCnt);
				readCnt = is.read(readBuf);
			}

			out.flush();

			bytes = out.toByteArray();
		}
		finally {
			try {
				is.close();
			}
			catch (final Exception e) {
				if (isDebugEnabled) {
					LOGGER.debug("An error occurs while closing stream", e);
				}
			}
		}

		return bytes;
	}
}
